using System;
using System.ComponentModel;
using System.Collections.Generic;
using System.Collections;
using System.Net;
using Microsoft.Dss.Core.Attributes;
using Microsoft.Ccr.Core;
using Microsoft.Dss.Core;
using Microsoft.Dss.ServiceModel.DsspServiceBase;
using Microsoft.Dss.ServiceModel.Dssp;
using submgr = Microsoft.Dss.Services.SubscriptionManager;
using Microsoft.Dss.Core.DsspHttp;
using W3C.Soap;
using i2c = Generic.Protocols.I2C.Proxy;

// TODO: This is service fairly is shoddy ... needs plenty of work

namespace Devantech.Services.GPIO14.Hardware
{
    /// <summary>
    /// Devantech GPIO14 Hardware Service
    /// </summary>
    [DisplayName("Devantech GPIO14Hardware")]
    [Description("Controls a number of general purpose IO modules.")]
    [Contract(Contract.Identifier)]
    public class DevantechGPIO14Hardware : DsspServiceBase
    {
        #region Partners and Ports

        [InitialStatePartner(Optional = true, ServiceUri = LayoutPaths.StoreDir + "Devantech.Services.GPIO14.Hardware.xml")]
        [ServiceState()]
        private StateType _state = null;

        private bool Initialized = false;

        [Partner("GenericI2C", Contract = i2c.Contract.Identifier, CreationPolicy = PartnerCreationPolicy.UseExistingOrCreate, Optional = false)]
        private i2c.GenericI2COperations _i2cPort = new i2c.GenericI2COperations();

        [SubscriptionManagerPartner]
        submgr.SubscriptionManagerPort _subMgrPort = new submgr.SubscriptionManagerPort();

        [ServicePort("/GPIO14Hardware", AllowMultipleInstances = false)]
        private GPIO14Operations _mainPort = new GPIO14Operations();

        // Hardware port
        private HardwareOperations _hardwarePort = new HardwareOperations();

        private Port<DateTime> _timerPort = new Port<DateTime>();

        #endregion

        #region Contructor and Start
        /// <summary>
        /// Initializes a new instance of the <see cref="DevantechGPIO14Hardware"/> class.
        /// </summary>
        /// <param name="creationPort">The creation port.</param>
        public DevantechGPIO14Hardware(DsspServiceCreationPort creationPort)
            :
                base(creationPort)
        {
        }

        /// <summary>
        /// The startup method for this Dss Service
        /// </summary>
        protected override void Start()
        {
            //initialize state
            if (_state == null)
            {
                _state = new StateType();
                _state.FirmwareVersion = 0;
                _state.Sensors = new GPIOState();
                base.SaveState(_state);
            }

            base.Start();

            // LogInfo("GPIO14Hardware service is waiting to connect to the device");
            //  if (!ConnectToGPIO14())
            //  {
            //     LogError(LogGroups.Console, "Error initializing GPIO14.");
            //     Shutdown();
            //    return;
            // }


            CompleteStartup();
        }

        /// <summary>
        /// Completes the startup.
        /// </summary>
        private void CompleteStartup()
        {
            LogInfo("GPIOHardware service is Connected to the device.");

            if (_state.EnableDigitalPolling ||
                _state.EnableAnalogue0Polling ||
                _state.EnableAnalogue1Polling ||
                _state.EnableAnalogue2Polling ||
                _state.EnableAnalogue3Polling ||
                _state.EnableAnalogue4Polling)
            {
                // Start a polling loop
                _timerPort.Post(DateTime.Now);
            }

            // Now listen on the main port for requests and call the appropriate handler.
            MainPortInterleave.CombineWith(
                Arbiter.Interleave(
                    new TeardownReceiverGroup(),
                    new ExclusiveReceiverGroup(),
                    new ConcurrentReceiverGroup
                    (
                        Arbiter.Receive(true, _timerPort, TimerHandler),
                        Arbiter.ReceiveWithIterator<UpdateSensors>(true, _hardwarePort, UpdateSensorsHandler)
                       )));
        }

        #endregion

        #region First Connect
        /// <summary>
        /// Connects to GPIO14.
        /// </summary>
        /// <returns></returns>
        private bool ConnectToGPIO14()
        {
            // Read the revision number
            i2c.ReadData readData = new i2c.ReadData();
            readData.Body.I2CAddress = _state.I2CAddress;
            readData.Body.RegisterAddress = 0;
            readData.Body.NumBytes = 1;
            _i2cPort.Post(readData);

            Activate(Arbiter.Choice(readData.ResponsePort, delegate(i2c.ReturnData bytes)
            {
                Initialized = true;
                _state.FirmwareVersion = bytes.Data[0];
            }, delegate(Fault f)
            {
                LogError(null,"Failed to get firmware version GPIO14 " , f);
                Initialized = false;
            }
            ));

            i2c.WriteData writeData = new i2c.WriteData();
            writeData.Body.I2CAddress = _state.I2CAddress;
            writeData.Body.RegisterAddress = (byte)GPIOWriteResgisters.AD_CONTROL;
            writeData.Body.Data = new byte[] { 128 };

            _i2cPort.Post(writeData);
            Activate(Arbiter.Choice(writeData.ResponsePort, delegate(DefaultUpdateResponseType dsrt)
            {
                Initialized = true;
                // all good
            }, delegate(Fault f)
            {
                LogError(null, "Failed to set analogue mode of GPIO14 ", f);
                Initialized = false;
            }
            ));

            return Initialized;
        }
        #endregion

        #region Polling

        private int RequestPending = 0;

        /// <summary>
        /// Update all the sensor information
        /// </summary>
        /// <param name="updateSensors"></param>
        /// <returns></returns>
        //[ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        public IEnumerator<ITask> UpdateSensorsHandler(UpdateSensors updateSensors)
        {
            if (RequestPending > 0)
            {
                LogVerbose("GPIO dumping update request");
            }

            RequestPending++;

            GPIOState newState = (GPIOState)_state.Sensors.Clone();

            // prepare the message
            i2c.ReadData readData = new i2c.ReadData();
            i2c.WriteData writeData = new i2c.WriteData();
            readData.Body.I2CAddress = _state.I2CAddress;

            if (_state.EnableDigitalPolling)
            {
                readData.Body.RegisterAddress = (byte)GPIOReadResgisters.PORT_A;
                readData.Body.NumBytes = 2;

                _i2cPort.Post(readData);

                // receive the result
                yield return Arbiter.Choice(readData.ResponsePort,
                    delegate(i2c.ReturnData returnData)
                    {
                        BitArray digi = new BitArray(returnData.Data);
                        newState.BIT_A_0 = digi[0];
                        newState.BIT_A_1 = digi[1];
                        newState.BIT_A_2 = digi[2];
                        newState.BIT_A_3 = digi[3];
                        newState.BIT_A_4 = digi[4];
                        newState.BIT_A_5 = digi[5];
                        newState.BIT_A_6 = digi[6];
                        newState.BIT_A_7 = digi[7];
                        newState.BIT_B_0 = digi[8];
                        newState.BIT_B_2 = digi[10];
                        newState.BIT_B_3 = digi[11];
                        newState.BIT_B_5 = digi[13];
                        newState.BIT_B_6 = digi[14];
                        newState.BIT_B_7 = digi[15];
                    },
                    delegate(Fault f)
                    {
                        LogError(null,"GPIO Error when trying to get port A " , f);
                    }
                    );
            }

            if (_state.EnableAnalogue0Polling)
            {
                // analogue 0 -------------------------------------------------------------------
                writeData.Body.I2CAddress = _state.I2CAddress;
                writeData.Body.RegisterAddress = (byte)GPIOWriteResgisters.COMMAND;
                writeData.Body.Data = new byte[] { (byte)GPIOCommands.GET_AD0 };

                readData.Body.I2CAddress = _state.I2CAddress;
                readData.Body.RegisterAddress = (byte)GPIOReadResgisters.RESULT_HIGH;
                readData.Body.NumBytes = 2;

                _i2cPort.Post(writeData);

                yield return (Arbiter.Choice(writeData.ResponsePort,
                    delegate(DefaultUpdateResponseType dsrt) { },
                    delegate(Fault f)
                    {
                        LogInfo("Fault while writting AN convertion command " + f.ToString());
                    }
                ));

                _i2cPort.Post(readData);

                int an_high = 0;
                int an_low = 0;
                int an_result = 0;

                // receive the result
                yield return Arbiter.Choice(readData.ResponsePort,
                     delegate(i2c.ReturnData returnData)
                     {
                         an_high = returnData.Data[0];
                         an_low = returnData.Data[1];
                         an_result = (an_high * 256) + an_low;
                         newState.AN_0 = an_result;
                     },
                 delegate(Fault f)
                 {
                     LogError(null,"error when trying to get analogue 0 High " ,f);
                 }
                 );
            }

            if (_state.EnableAnalogue1Polling)
            {

                // analogue 1 -------------------------------------------------------------------
                writeData.Body.I2CAddress = _state.I2CAddress;
                writeData.Body.RegisterAddress = (byte)GPIOWriteResgisters.COMMAND;
                writeData.Body.Data = new byte[] { (byte)GPIOCommands.GET_AD1 };

                readData.Body.I2CAddress = _state.I2CAddress;
                readData.Body.RegisterAddress = (byte)GPIOReadResgisters.RESULT_HIGH;
                readData.Body.NumBytes = 2;

                _i2cPort.Post(writeData);
                _i2cPort.Post(readData);


                // receive the result
                yield return Arbiter.Choice(readData.ResponsePort,
                    delegate(i2c.ReturnData returnData)
                    {
                        newState.AN_1 = (returnData.Data[0] * 256) + returnData.Data[1];
                    },
                 delegate(Fault f)
                 {
                     LogError(null,"error when trying to get analogue 0 High ", f);
                 }
                 );
            }

            if (_state.EnableAnalogue2Polling)
            {
                // analogue 2 -------------------------------------------------------------------
                writeData.Body.I2CAddress = _state.I2CAddress;
                writeData.Body.RegisterAddress = (byte)GPIOWriteResgisters.COMMAND;
                writeData.Body.Data = new byte[] { (byte)GPIOCommands.GET_AD2 };

                readData.Body.I2CAddress = _state.I2CAddress;
                readData.Body.RegisterAddress = (byte)GPIOReadResgisters.RESULT_HIGH;
                readData.Body.NumBytes = 2;

                _i2cPort.Post(writeData);
                _i2cPort.Post(readData);

                // receive the result
                yield return Arbiter.Choice(readData.ResponsePort,
                    delegate(i2c.ReturnData returnData)
                    {
                        newState.AN_2 = (returnData.Data[0] * 256) + returnData.Data[1];
                    },
                 delegate(Fault f)
                 {
                     LogError(null,"error when trying to get analogue 0 High ", f);
                 }

                 );
            }

            if (_state.EnableAnalogue3Polling)
            {
                // analogue 3 -------------------------------------------------------------------
                writeData.Body.I2CAddress = _state.I2CAddress;
                writeData.Body.RegisterAddress = (byte)GPIOWriteResgisters.COMMAND;
                writeData.Body.Data = new byte[] { (byte)GPIOCommands.GET_AD3 };

                readData.Body.I2CAddress = _state.I2CAddress;
                readData.Body.RegisterAddress = (byte)GPIOReadResgisters.RESULT_HIGH;
                readData.Body.NumBytes = 2;

                _i2cPort.Post(writeData);
                _i2cPort.Post(readData);


                // receive the result
                yield return Arbiter.Choice(readData.ResponsePort,
                 delegate(i2c.ReturnData returnData)
                 {
                     newState.AN_3 = (returnData.Data[0] * 256) + returnData.Data[1];
                 },
                delegate(Fault f)
                {
                    LogError(null,"error when trying to get analogue 0 High ", f);
                }

                 );
            }

            if (_state.EnableAnalogue4Polling)
            {

                // analogue 4 -------------------------------------------------------------------
                writeData.Body.I2CAddress = _state.I2CAddress;
                writeData.Body.RegisterAddress = (byte)GPIOWriteResgisters.COMMAND;
                writeData.Body.Data = new byte[] { (byte)GPIOCommands.GET_AD4 };

                readData.Body.I2CAddress = _state.I2CAddress;
                readData.Body.RegisterAddress = (byte)GPIOReadResgisters.RESULT_HIGH;
                readData.Body.NumBytes = 2;

                _i2cPort.Post(writeData);
                _i2cPort.Post(readData);

                // receive the result
                yield return Arbiter.Choice(readData.ResponsePort,

                      delegate(i2c.ReturnData returnData)
                      {
                          newState.AN_4 = (returnData.Data[0] * 256) + returnData.Data[1];
                      },
                      delegate(Fault f)
                      {
                          LogError(null,"error when trying to get analogue 0 High ", f);
                      }
                 );
            }

            newState.Timestamp = DateTime.Now;
            RequestPending--;

            // report success
            updateSensors.ResponsePort.Post(newState);
        }


        /// <summary>
        /// Timers the handler.
        /// </summary>
        /// <param name="signal">The signal.</param>
        void TimerHandler(DateTime signal)
        {
            if (_state.EnableDigitalPolling ||
               _state.EnableAnalogue0Polling ||
               _state.EnableAnalogue1Polling ||
               _state.EnableAnalogue2Polling ||
               _state.EnableAnalogue3Polling ||
               _state.EnableAnalogue4Polling)
            {
                UpdateSensors updateSensors = new UpdateSensors();
                _hardwarePort.Post(updateSensors);

                Activate(Arbiter.Choice(updateSensors.ResponsePort,
                delegate(GPIOState newState)
                {
                    // send an internal message to update the state
                    SensorInput newSensorData = new SensorInput();
                    newSensorData.Body = newState;
                    _mainPort.Post(newSensorData);
                },
                delegate(Fault f)
                {
                    LogError(null, "Failure waiting for the getAllRequest", f);
                }
            ));

                // update sensors every quarter of a second
                Activate(
                    Arbiter.Receive(false, TimeoutPort(_state.PollDelay),
                        delegate(DateTime time)
                        {
                            _timerPort.Post(time);
                        }
                    )
                );
            }
        }

        /// <summary>
        /// Sensors the input handler.
        /// </summary>
        /// <param name="sensorInput">The sensor input.</param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        public IEnumerator<ITask> SensorInputHandler(SensorInput sensorInput)
        {
            // merge in the new state and set the change flags
            _state.Sensors.MergeNewState(sensorInput.Body);

            // Send notifications out
            if (_subMgrPort != null)
            {
                List<string> notify = new List<string>();

                if (_state.Sensors.AN_0_CHANGED) notify.Add("AN_0");
                if (_state.Sensors.AN_1_CHANGED) notify.Add("AN_1");
                if (_state.Sensors.AN_2_CHANGED) notify.Add("AN_2");
                if (_state.Sensors.AN_3_CHANGED) notify.Add("AN_3");
                if (_state.Sensors.AN_4_CHANGED) notify.Add("AN_4");
                if (_state.Sensors.BIT_A_0_CHANGED) notify.Add("BIT_A_0");
                if (_state.Sensors.BIT_A_1_CHANGED) notify.Add("BIT_A_1");
                if (_state.Sensors.BIT_A_2_CHANGED) notify.Add("BIT_A_2");
                if (_state.Sensors.BIT_A_3_CHANGED) notify.Add("BIT_A_3");
                if (_state.Sensors.BIT_A_4_CHANGED) notify.Add("BIT_A_4");
                if (_state.Sensors.BIT_A_5_CHANGED) notify.Add("BIT_A_5");
                if (_state.Sensors.BIT_A_6_CHANGED) notify.Add("BIT_A_6");
                if (_state.Sensors.BIT_A_7_CHANGED) notify.Add("BIT_A_7");
                if (_state.Sensors.BIT_B_0_CHANGED) notify.Add("BIT_B_0");
                if (_state.Sensors.BIT_B_2_CHANGED) notify.Add("BIT_B_2");
                if (_state.Sensors.BIT_B_3_CHANGED) notify.Add("BIT_B_3");
                if (_state.Sensors.BIT_B_5_CHANGED) notify.Add("BIT_B_5");
                if (_state.Sensors.BIT_B_6_CHANGED) notify.Add("BIT_B_6");
                if (_state.Sensors.BIT_B_7_CHANGED) notify.Add("BIT_B_7");

                // notify selective subscribers

                submgr.Submit sub = new submgr.Submit(_state, DsspActions.ReplaceRequest, notify.ToArray());
                _subMgrPort.Post(sub);

                // notify general subscribers
                _subMgrPort.Post(new submgr.Submit(_state, DsspActions.ReplaceRequest));

                if (notify.Count > 0)
                {
                    string strList = "";
                    foreach (string s in notify)
                    {
                        strList += s + " ";
                    }
                    LogInfo("Notify: " + strList);
                }


            }
            if (sensorInput.ResponsePort != null)
            {
                sensorInput.ResponsePort.Post(DefaultUpdateResponseType.Instance);
            }
            yield break;
        }
        #endregion

        #region Standard Handlers

        /// <summary>
        /// Replaces the handler.
        /// </summary>
        /// <param name="replace">The replace.</param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        public IEnumerator<ITask> ReplaceHandler(Replace replace)
        {
            _state = replace.Body;
            base.SaveState(_state);
            if (replace.ResponsePort != null)
            {
                replace.ResponsePort.Post(DefaultReplaceResponseType.Instance);
            }
            yield break;
        }

        #endregion

        #region Subscriptions

        /// <summary>
        /// Subscribes the handler.
        /// </summary>
        /// <param name="subscribe">The subscribe.</param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        public IEnumerator<ITask> SubscribeHandler(Subscribe subscribe)
        {
            SubscribeRequestType request = subscribe.Body;
            LogInfo("Subscribe request from: " + request.Subscriber);

            yield return Arbiter.Choice(
            SubscribeHelper(_subMgrPort, request, subscribe.ResponsePort),
            delegate(SuccessResult success)
            {
                base.SendNotificationToTarget<Replace>(request.Subscriber, _subMgrPort, _state);
            },
              delegate(Exception e)
              {
                  LogError(null, "GPIO14 Subscribe failed", e);
              }
            );

            yield break;
        }

        /// <summary>
        /// Selectives the subscribe handler.
        /// </summary>
        /// <param name="subRequest">The sub request.</param>
        /// <returns></returns>
        [ServiceHandler(ServiceHandlerBehavior.Exclusive)]
        public IEnumerator<ITask> SelectiveSubscribeHandler(SelectiveSubscribe subRequest)
        {
            LogInfo("GPIO14 Received a subscription request from " + subRequest.Body.Subscriber);
            submgr.InsertSubscription ins = new submgr.InsertSubscription(
                new submgr.InsertSubscriptionMessage(
                subRequest.Body.Subscriber,
                subRequest.Body.Expiration,
                0));

            ins.Body.NotificationCount = subRequest.Body.NotificationCount;

            List<submgr.QueryType> subscribeFilter = new List<submgr.QueryType>();

            if (subRequest.Body.Names == null)
            {
                LogInfo("GPIO14 is adding all sensors to subscription from " + subRequest.Body.Subscriber);

                subscribeFilter.Add(new submgr.QueryType("AN_0"));
                subscribeFilter.Add(new submgr.QueryType("AN_1"));
                subscribeFilter.Add(new submgr.QueryType("AN_2"));
                subscribeFilter.Add(new submgr.QueryType("AN_3"));
                subscribeFilter.Add(new submgr.QueryType("AN_4"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_0"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_1"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_2"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_3"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_4"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_5"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_6"));
                subscribeFilter.Add(new submgr.QueryType("BIT_A_7"));
                subscribeFilter.Add(new submgr.QueryType("BIT_B_0"));
                subscribeFilter.Add(new submgr.QueryType("BIT_B_2"));
                subscribeFilter.Add(new submgr.QueryType("BIT_B_3"));
                subscribeFilter.Add(new submgr.QueryType("BIT_B_5"));
                subscribeFilter.Add(new submgr.QueryType("BIT_B_6"));
                subscribeFilter.Add(new submgr.QueryType("BIT_B_7"));
            }
            else
            {
                foreach (string s in subRequest.Body.Names)
                {
                    LogInfo("GPIO14 is adding subscription for " + s.ToUpper() + " from " + subRequest.Body.Subscriber);
                    subscribeFilter.Add(new submgr.QueryType(s.ToUpper()));
                }
            }

            ins.Body.QueryList = subscribeFilter.ToArray();
            _subMgrPort.Post(ins);

            yield return Arbiter.Choice(ins.ResponsePort,
            delegate(SubscribeResponseType rsp)
            {
                LogInfo("GPIO14 added subcription to manager");
                subRequest.ResponsePort.Post(rsp);
            },
            delegate(Fault f)
            {
                LogError(null, "GPIO14 failed to add to subscribe manager", f);
                subRequest.ResponsePort.Post(f);
            });
            yield break;

        }
        #endregion
    }

    #region GPIO14 Enums
    /// registers that can be written to
    enum GPIOReadResgisters : byte
    {
        SOFTWARE_REVISION_NUMBER = (byte)0,
        RESULT_HIGH = (byte)1,
        RESULT_LOW = (byte)2,
        AD_CONTROL = (byte)3,
        PORT_A = (byte)4,
        PORT_B = (byte)5,
        PWM = (byte)6,
        I2C_ADDRESS_CHANGE = (byte)7
    }

    /// registers that can be read from
    enum GPIOWriteResgisters : byte
    {
        COMMAND = (byte)0,
        PORT_A_MASK = (byte)1,
        PORT_B_MASK = (byte)2,
        AD_CONTROL = (byte)3,
        PORT_A = (byte)4,
        PORT_B = (byte)5,
        PWM = (byte)6,
        I2C_ADDRESS_CHANGE = (byte)7
    }

    /// commands to send to the command register
    enum GPIOCommands : byte
    {
        GET_AD0 = (byte)3,
        GET_AD1 = (byte)4,
        GET_AD2 = (byte)5,
        GET_AD3 = (byte)6,
        GET_AD4 = (byte)7
    }
    #endregion
} 

